package speed2010.andreza.vspeed.view;

import java.awt.Cursor;
import java.awt.event.MouseEvent;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.util.Hashtable;
import java.util.Iterator;

import javax.swing.SwingUtilities;

import prefuse.Constants;
import prefuse.Display;
import prefuse.Visualization;
import prefuse.action.ActionList;
import prefuse.action.RepaintAction;
import prefuse.action.assignment.ColorAction;
import prefuse.action.assignment.DataColorAction;
import prefuse.action.layout.Layout;
import prefuse.activity.Activity;
import prefuse.controls.ControlAdapter;
import prefuse.controls.PanControl;
import prefuse.controls.ZoomControl;
import prefuse.data.Graph;
import prefuse.data.Node;
import prefuse.data.Schema;
import prefuse.data.expression.Predicate;
import prefuse.data.expression.parser.ExpressionParser;
import prefuse.data.tuple.TupleSet;
import prefuse.render.AbstractShapeRenderer;
import prefuse.render.DefaultRendererFactory;
import prefuse.render.LabelRenderer;
import prefuse.render.PolygonRenderer;
import prefuse.render.Renderer;
import prefuse.util.ColorLib;
import prefuse.util.GraphicsLib;
import prefuse.util.PrefuseLib;
import prefuse.util.force.DragForce;
import prefuse.util.force.ForceItem;
import prefuse.util.force.ForceSimulator;
import prefuse.util.force.NBodyForce;
import prefuse.util.force.SpringForce;
import prefuse.visual.AggregateItem;
import prefuse.visual.AggregateTable;
import prefuse.visual.EdgeItem;
import prefuse.visual.NodeItem;
import prefuse.visual.VisualGraph;
import prefuse.visual.VisualItem;

/**
 * Demo application showcasing the use of AggregateItems to visualize groupings of nodes with in a graph visualization.
 * 
 * This class uses the AggregateLayout class to compute bounding polygons for each aggregate and the
 * AggregateDragControl to enable drags of both nodes and node aggregates.
 * 
 * @author <a href="http://jheer.org">jeffrey heer</a>
 */
public class PrefuseAggregate extends Display {
	private static final long serialVersionUID = 6655360013778680735L;

	public static final String GRAPH = "graph";
	public static final String NODES = "graph.nodes";
	public static final String EDGES = "graph.edges";
	public static final String AGGR = "aggregates";

	public PrefuseAggregate(final String selectValue, final Hashtable<Integer, String> ident_tipo,
			final Hashtable<Integer, String> ident_name, final Hashtable<Integer, Integer> ps_ps,
			final Hashtable<Integer, Integer> pd_pi, final Hashtable<Integer, Integer> pi_ps) {
		// initialize display and data
		super(new Visualization());
		initDataGroups(ident_tipo, ident_name, ps_ps, pd_pi, pi_ps);

		//TODO Provisorio até armazer no Communication Manager o Address do PD
		final String sourceValue = "2378";
		
		// set up the renderers
		// draw the nodes as basic shapes
		// Renderer nodeR = new ShapeRenderer(20);
		final LabelRenderer m_nodeRenderer = new MyLabelRenderer();
		m_nodeRenderer.setRenderType(AbstractShapeRenderer.RENDER_TYPE_DRAW_AND_FILL);
		m_nodeRenderer.setHorizontalAlignment(Constants.LEFT);
		m_nodeRenderer.setRoundedCorner(8, 8);

		// draw aggregates as polygons with curved edges
		final Renderer polyR = new PolygonRenderer(Constants.POLY_TYPE_CURVE);
		((PolygonRenderer) polyR).setCurveSlack(0.15f);

		final DefaultRendererFactory drf = new DefaultRendererFactory();

		drf.setDefaultRenderer(m_nodeRenderer);

		drf.add("ingroup('aggregates')", polyR);

		this.m_vis.setRendererFactory(drf);

		final ColorAction nText = new ColorAction(NODES, VisualItem.TEXTCOLOR);
		nText.setDefaultColor(ColorLib.rgb(100, 110, 330));

		// set up the visual operators
		// first set up all the color actions
		final ColorAction nStroke = new ColorAction(NODES, VisualItem.STROKECOLOR);
		nStroke.setDefaultColor(ColorLib.gray(100));
		nStroke.add("_hover", ColorLib.gray(50));

		final ColorAction nFill = new ColorAction(NODES, VisualItem.FILLCOLOR);
		nFill.setDefaultColor(ColorLib.gray(255));
		nFill.add("_hover", ColorLib.gray(200));

		ColorAction rFill = null;
		ColorAction sFill = null;
		
		int[] palette = null;
		if (selectValue != null) {
			final String condition = "ident=" + selectValue;
			final Predicate predicate = ExpressionParser.predicate(condition);
			rFill = new ColorAction(NODES, predicate, VisualItem.FILLCOLOR);
			rFill.setDefaultColor(PrefuseConstants.NODE_RESULT_SOURCE);

			palette = new int[]{PrefuseConstants.NODE_RESULT_PEERS};
		} 
		
		if (sourceValue != null) {
			final String condition = "ident=" + sourceValue;
			final Predicate predicate = ExpressionParser.predicate(condition);
			sFill = new ColorAction(NODES, predicate, VisualItem.FILLCOLOR);
			sFill.setDefaultColor(PrefuseConstants.NODE_COLOR_SEARCH);

			palette = new int[]{PrefuseConstants.NODE_RESULT_PEERS};
		}
		
		if (selectValue == null || sourceValue == null) {
			palette = new int[]{ColorLib.rgba(255, 255, 204, 150), ColorLib.rgba(204, 255, 255, 150),
					ColorLib.rgba(204, 204, 255, 150), ColorLib.rgba(204, 255, 204, 150),
					ColorLib.rgba(204, 255, 255, 150), ColorLib.rgba(204, 255, 204, 150),
					ColorLib.rgba(255, 255, 153, 150), ColorLib.rgba(153, 204, 255, 150),
					ColorLib.rgba(255, 153, 204, 150), ColorLib.rgba(204, 153, 255, 150),
					ColorLib.rgba(255, 204, 153, 150)};
		}
		
		final ColorAction nEdges = new ColorAction(EDGES, VisualItem.STROKECOLOR);
		nEdges.setDefaultColor(ColorLib.gray(100));

		final ColorAction aStroke = new ColorAction(AGGR, VisualItem.STROKECOLOR);
		aStroke.setDefaultColor(ColorLib.gray(200));
		aStroke.add("_hover", ColorLib.rgb(255, 100, 100));

		final ColorAction aFill = new DataColorAction(AGGR, "id", Constants.NOMINAL, VisualItem.FILLCOLOR, palette);

		// bundle the color actions
		final ActionList colors = new ActionList();

		colors.add(nText);
		colors.add(nStroke);
		colors.add(nFill);
		colors.add(nEdges);
		colors.add(aStroke);
		colors.add(aFill);

		if (rFill != null) {
			colors.add(rFill);
		}

		if (sFill != null && rFill != null) {
			colors.add(sFill);
		}
		
		// now create the main layout routine
		final ActionList layout = new ActionList(Activity.INFINITY);
		layout.add(colors);
		// ForceDirectedLayout fdl = new ForceDirectedLayout(GRAPH, true);
		// layout.add(fdl);

		final ForceDirectedLayoutMagic fdlm = new ForceDirectedLayoutMagic(GRAPH);
		layout.add(fdlm);

		layout.add(new AggregateLayout(AGGR));
		layout.add(new RepaintAction());
		this.m_vis.putAction("layout", layout);

		// set up the display
		setSize(700, 600);
		// d.zoom(new Point2D.Double(),1.5); //Aplica zoom no grafico
		animatePan(300, 300, 1);

		setHighQuality(true);
		addControlListener(new AggregateDragControl());
		addControlListener(new ZoomControl());
		addControlListener(new PanControl());

		// set things running
		this.m_vis.run("layout");
	}

	static public void configuraNo(final Node n, final String label, final String tipo, final int id) {
		n.setString("label", label);
		n.set("ident", id);
		n.set("ftype", new Flavour(tipo));
	}

	private void initDataGroups(final Hashtable<Integer, String> ident_tipo,
			final Hashtable<Integer, String> ident_name, final Hashtable<Integer, Integer> ps_ps,
			final Hashtable<Integer, Integer> pd_pi, final Hashtable<Integer, Integer> pi_ps) {

		final Graph g = new Graph();
		g.addColumn("label", String.class);
		g.addColumn("ident", Integer.class);
		g.addColumn("ftype", Flavour.class);

		// cria e prepara os nós
		for (final Integer ident : ident_tipo.keySet()) {
			final Node n1 = g.addNode();
			configuraNo(n1, ident_name.get(ident), ident_tipo.get(ident), ident);
		}

		// // ligações dentro dos grupos (PD)
		final Hashtable<Integer, Integer> nodeIdent_nodeID = new Hashtable<Integer, Integer>();

		for (int i = 0; i < g.getNodeCount(); i++) {
			final Node n = g.getNode(i);
			final int nodeIdent = Integer.parseInt(n.get(1) + "");
			nodeIdent_nodeID.put(nodeIdent, i);
		}

		// ligações entre os grupos (PS)
		for (final Integer source : ps_ps.keySet()) {
			final int sourceNodeId = nodeIdent_nodeID.get(source);
			final int targetNodeId = nodeIdent_nodeID.get(ps_ps.get(source));
			g.addEdge(sourceNodeId, targetNodeId);
		}

		// ligações entre os grupos (PS e PI)
		for (final Integer source : pi_ps.keySet()) {
			final int sourceNodeId = nodeIdent_nodeID.get(source);
			final int targetNodeId = nodeIdent_nodeID.get(pi_ps.get(source));
			g.addEdge(sourceNodeId, targetNodeId);
		}

		// ligações entre os grupos (PI e PD)
		for (final Integer source : pd_pi.keySet()) {
			final int sourceNodeId = nodeIdent_nodeID.get(source);
			final int targetNodeId = nodeIdent_nodeID.get(pd_pi.get(source));
			g.addEdge(sourceNodeId, targetNodeId);
		}

		// add visual data groups
		final VisualGraph vg = this.m_vis.addGraph(GRAPH, g);

		this.m_vis.setInteractive(EDGES, null, false);

		this.m_vis.setValue(NODES, null, VisualItem.SHAPE, new Integer(Constants.SHAPE_ELLIPSE));

		final AggregateTable at = this.m_vis.addAggregates(AGGR);
		at.addColumn(VisualItem.POLYGON, float[].class);
		at.addColumn("id", int.class);

		// add nodes to aggregates

		for (final Integer pi : pi_ps.keySet()) { // iterar os PI
			final int piNodeId = nodeIdent_nodeID.get(pi);

			final AggregateItem aitem = (AggregateItem) at.addItem();
			aitem.setInt("id", piNodeId);
			aitem.addItem((VisualItem) vg.getNode(piNodeId));

			for (final Integer pd : pd_pi.keySet()) {// Iterar os PD
				final int piTemp = pd_pi.get(pd);
				if (pi == piTemp) {
					final int pdNodeId = nodeIdent_nodeID.get(pd);
					aitem.addItem((VisualItem) vg.getNode(pdNodeId)); //classCastException
				}
			}
		}
	}
} // end of class AggregateDemo

/**
 * Layout algorithm that computes a convex hull surrounding aggregate items and saves it in the "_polygon" field.
 */
class AggregateLayout extends Layout {

	private final int m_margin = 5; // convex hull pixel margin
	private double[] m_pts; // buffer for computing convex hulls

	public AggregateLayout(final String aggrGroup) {
		super(aggrGroup);
	}

	/**
	 * @see edu.berkeley.guir.prefuse.action.Action#run(edu.berkeley.guir.prefuse.ItemRegistry, double)
	 */
	@Override
	public void run(final double frac) {

		final AggregateTable aggr = (AggregateTable) this.m_vis.getGroup(this.m_group);
		// do we have any to process?
		final int num = aggr.getTupleCount();
		if (num == 0) {
			return;
		}

		// update buffers
		int maxsz = 0;
		for (final Iterator<?> aggrs = aggr.tuples(); aggrs.hasNext();) {
			maxsz = Math.max(maxsz, 4 * 2 * ((AggregateItem) aggrs.next()).getAggregateSize());
		}
		if ((this.m_pts == null) || (maxsz > this.m_pts.length)) {
			this.m_pts = new double[maxsz];
		}

		// compute and assign convex hull for each aggregate
		final Iterator<?> aggrs = this.m_vis.visibleItems(this.m_group);
		while (aggrs.hasNext()) {
			final AggregateItem aitem = (AggregateItem) aggrs.next();

			int idx = 0;
			if (aitem.getAggregateSize() == 0) {
				continue;
			}
			VisualItem item = null;
			final Iterator<?> iter = aitem.items();
			while (iter.hasNext()) {
				item = (VisualItem) iter.next();
				if (item.isVisible()) {
					addPoint(this.m_pts, idx, item, this.m_margin);
					idx += 2 * 4;
				}
			}
			// if no aggregates are visible, do nothing
			if (idx == 0) {
				continue;
			}

			// compute convex hull
			final double[] nhull = GraphicsLib.convexHull(this.m_pts, idx);

			// prepare viz attribute array
			float[] fhull = (float[]) aitem.get(VisualItem.POLYGON);
			if ((fhull == null) || (fhull.length < nhull.length)) {
				fhull = new float[nhull.length];
			} else if (fhull.length > nhull.length) {
				fhull[nhull.length] = Float.NaN;
			}

			// copy hull values
			for (int j = 0; j < nhull.length; j++) {
				fhull[j] = (float) nhull[j];
			}
			aitem.set(VisualItem.POLYGON, fhull);
			aitem.setValidated(false); // force invalidation
		}
	}

	private static void addPoint(final double[] pts, final int idx, final VisualItem item, final int growth) {
		final Rectangle2D b = item.getBounds();
		final double minX = (b.getMinX()) - growth, minY = (b.getMinY()) - growth;
		final double maxX = (b.getMaxX()) + growth, maxY = (b.getMaxY()) + growth;
		pts[idx] = minX;
		pts[idx + 1] = minY;
		pts[idx + 2] = minX;
		pts[idx + 3] = maxY;
		pts[idx + 4] = maxX;
		pts[idx + 5] = minY;
		pts[idx + 6] = maxX;
		pts[idx + 7] = maxY;
	}

} // end of class AggregateLayout

/**
 * Interactive drag control that is "aggregate-aware"
 */
class AggregateDragControl extends ControlAdapter {

	private VisualItem activeItem;
	protected Point2D down = new Point2D.Double();
	protected Point2D temp = new Point2D.Double();
	protected boolean dragged;

	/**
	 * Creates a new drag control that issues repaint requests as an item is dragged.
	 */
	public AggregateDragControl() {
	}

	/**
	 * @see prefuse.controls.Control#itemEntered(prefuse.visual.VisualItem, java.awt.event.MouseEvent)
	 */
	@Override
	public void itemEntered(final VisualItem item, final MouseEvent e) {
		final Display d = (Display) e.getSource();
		d.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
		this.activeItem = item;
		if (!(item instanceof AggregateItem)) {
			setFixed(item, true);
		}
	}

	/**
	 * @see prefuse.controls.Control#itemExited(prefuse.visual.VisualItem, java.awt.event.MouseEvent)
	 */
	@Override
	public void itemExited(final VisualItem item, final MouseEvent e) {
		if (this.activeItem == item) {
			this.activeItem = null;
			setFixed(item, false);
		}
		final Display d = (Display) e.getSource();
		d.setCursor(Cursor.getDefaultCursor());
	}

	/**
	 * @see prefuse.controls.Control#itemPressed(prefuse.visual.VisualItem, java.awt.event.MouseEvent)
	 */
	@Override
	public void itemPressed(final VisualItem item, final MouseEvent e) {
		if (!SwingUtilities.isLeftMouseButton(e)) {
			return;
		}
		this.dragged = false;
		final Display d = (Display) e.getComponent();
		d.getAbsoluteCoordinate(e.getPoint(), this.down);
		if (item instanceof AggregateItem) {
			setFixed(item, true);
		}
	}

	/**
	 * @see prefuse.controls.Control#itemReleased(prefuse.visual.VisualItem, java.awt.event.MouseEvent)
	 */
	@Override
	public void itemReleased(final VisualItem item, final MouseEvent e) {
		if (!SwingUtilities.isLeftMouseButton(e)) {
			return;
		}
		if (this.dragged) {
			this.activeItem = null;
			setFixed(item, false);
			this.dragged = false;
		}
	}

	/**
	 * @see prefuse.controls.Control#itemDragged(prefuse.visual.VisualItem, java.awt.event.MouseEvent)
	 */
	@Override
	public void itemDragged(final VisualItem item, final MouseEvent e) {
		if (!SwingUtilities.isLeftMouseButton(e)) {
			return;
		}
		this.dragged = true;
		final Display d = (Display) e.getComponent();
		d.getAbsoluteCoordinate(e.getPoint(), this.temp);
		final double dx = this.temp.getX() - this.down.getX();
		final double dy = this.temp.getY() - this.down.getY();

		move(item, dx, dy);

		this.down.setLocation(this.temp);
	}

	protected static void setFixed(final VisualItem item, final boolean fixed) {
		if (item instanceof AggregateItem) {
			final Iterator<?> items = ((AggregateItem) item).items();
			while (items.hasNext()) {
				setFixed((VisualItem) items.next(), fixed);
			}
		} else {
			item.setFixed(fixed);
		}
	}

	protected static void move(final VisualItem item, final double dx, final double dy) {
		if (item instanceof AggregateItem) {
			final Iterator<?> items = ((AggregateItem) item).items();
			while (items.hasNext()) {
				move((VisualItem) items.next(), dx, dy);
			}
		} else {
			final double x = item.getX();
			final double y = item.getY();
			item.setStartX(x);
			item.setStartY(y);
			item.setX(x + dx);
			item.setY(y + dy);
			item.setEndX(x + dx);
			item.setEndY(y + dy);
		}
	}

} // end of class AggregateDragControl

class Flavour {
	String ftype;

	public Flavour(final String ftype) {
		this.ftype = ftype;
	}

	public String getFlavourType() {
		return this.ftype;
	}
}

class MyLabelRenderer extends LabelRenderer {
	@Override
	public String getText(final VisualItem vi) {
		return vi.getString("label") + "\n" + ((Flavour) vi.get("ftype")).getFlavourType();
	}
}

class ForceDirectedLayoutMagic extends Layout {
	private boolean magicNodes = true;
	private boolean magicEdges = true;

	private ForceSimulator m_fsim;
	private long m_lasttime = -1L;
	private long m_maxstep = 50L; // 50L
	private final boolean m_runonce;
	private int m_iterations = 100;
	private final boolean m_enforceBounds;

	protected transient VisualItem referrer;

	protected String m_nodeGroup;
	protected String m_edgeGroup;

	/**
	 * Create a new ForceDirectedLayoutMagic. By default, this layout will not restrict the layout to the layout bounds
	 * and will assume it is being run in animated (rather than run-once) fashion.
	 * 
	 * @param graph
	 *            the data group to layout. Must resolve to a Graph instance.
	 */
	public ForceDirectedLayoutMagic(final String graph) {
		this(graph, true, false);
	}

	/**
	 * Create a new ForceDirectedLayoutMagic. The layout will assume it is being run in animated (rather than run-once)
	 * fashion.
	 * 
	 * @param group
	 *            the data group to layout. Must resolve to a Graph instance.
	 * @param enforceBounds
	 *            indicates whether or not the layout should require that all node placements stay within the layout
	 *            bounds.
	 */
	public ForceDirectedLayoutMagic(final String group, final boolean enforceBounds) {
		this(group, enforceBounds, false);
	}

	/**
	 * Create a new ForceDirectedLayoutMagic.
	 * 
	 * @param group
	 *            the data group to layout. Must resolve to a Graph instance.
	 * @param enforceBounds
	 *            indicates whether or not the layout should require that all node placements stay within the layout
	 *            bounds.
	 * @param runonce
	 *            indicates if the layout will be run in a run-once or animated fashion. In run-once mode, the layout
	 *            will run for a set number of iterations when invoked. In animation mode, only one iteration of the
	 *            layout is computed.
	 */
	public ForceDirectedLayoutMagic(final String group, final boolean enforceBounds, final boolean runonce) {
		super(group);
		this.m_nodeGroup = PrefuseLib.getGroupName(group, Graph.NODES);
		this.m_edgeGroup = PrefuseLib.getGroupName(group, Graph.EDGES);

		this.m_enforceBounds = enforceBounds;
		this.m_runonce = runonce;
		this.m_fsim = new ForceSimulator();
		this.m_fsim.addForce(new NBodyForce());
		this.m_fsim.addForce(new SpringForce());
		this.m_fsim.addForce(new DragForce());
	}

	/**
	 * Create a new ForceDirectedLayoutMagic. The layout will assume it is being run in animated (rather than run-once)
	 * fashion.
	 * 
	 * @param group
	 *            the data group to layout. Must resolve to a Graph instance.
	 * @param fsim
	 *            the force simulator used to drive the layout computation
	 * @param enforceBounds
	 *            indicates whether or not the layout should require that all node placements stay within the layout
	 *            bounds.
	 */
	public ForceDirectedLayoutMagic(final String group, final ForceSimulator fsim, final boolean enforceBounds) {
		this(group, fsim, enforceBounds, false);
	}

	/**
	 * Create a new ForceDirectedLayoutMagic.
	 * 
	 * @param group
	 *            the data group to layout. Must resolve to a Graph instance.
	 * @param fsim
	 *            the force simulator used to drive the layout computation
	 * @param enforceBounds
	 *            indicates whether or not the layout should require that all node placements stay within the layout
	 *            bounds.
	 * @param runonce
	 *            indicates if the layout will be run in a run-once or animated fashion. In run-once mode, the layout
	 *            will run for a set number of iterations when invoked. In animation mode, only one iteration of the
	 *            layout is computed.
	 */
	public ForceDirectedLayoutMagic(final String group, final ForceSimulator fsim, final boolean enforceBounds,
			final boolean runonce) {
		super(group);
		this.m_nodeGroup = PrefuseLib.getGroupName(group, Graph.NODES);
		this.m_edgeGroup = PrefuseLib.getGroupName(group, Graph.EDGES);

		this.m_enforceBounds = enforceBounds;
		this.m_runonce = runonce;
		this.m_fsim = fsim;
	}

	// ------------------------------------------------------------------------

	/**
	 * Get the maximum timestep allowed for integrating node settings between runs of this layout. When computation
	 * times are longer than desired, and node positions are changing dramatically between animated frames, the max step
	 * time can be lowered to suppress node movement.
	 * 
	 * @return the maximum timestep allowed for integrating between two layout steps.
	 */
	public long getMaxTimeStep() {
		return this.m_maxstep;
	}

	/**
	 * Set the maximum timestep allowed for integrating node settings between runs of this layout. When computation
	 * times are longer than desired, and node positions are changing dramatically between animated frames, the max step
	 * time can be lowered to suppress node movement.
	 * 
	 * @param maxstep
	 *            the maximum timestep allowed for integrating between two layout steps
	 */
	public void setMaxTimeStep(final long maxstep) {
		this.m_maxstep = maxstep;
	}

	/**
	 * Get the force simulator driving this layout.
	 * 
	 * @return the force simulator
	 */
	public ForceSimulator getForceSimulator() {
		return this.m_fsim;
	}

	/**
	 * Set the force simulator driving this layout.
	 * 
	 * @param fsim
	 *            the force simulator
	 */
	public void setForceSimulator(final ForceSimulator fsim) {
		this.m_fsim = fsim;
	}

	/**
	 * Get the number of iterations to use when computing a layout in run-once mode.
	 * 
	 * @return the number of layout iterations to run
	 */
	public int getIterations() {
		return this.m_iterations;
	}

	/**
	 * Set the number of iterations to use when computing a layout in run-once mode.
	 * 
	 * @param iter
	 *            the number of layout iterations to run
	 */
	public void setIterations(final int iter) {
		if (iter < 1) {
			throw new IllegalArgumentException("Iterations must be a positive number!");
		}
		this.m_iterations = iter;
	}

	/**
	 * Explicitly sets the node and edge groups to use for this layout, overriding the group setting passed to the
	 * constructor.
	 * 
	 * @param nodeGroup
	 *            the node data group
	 * @param edgeGroup
	 *            the edge data group
	 */
	public void setDataGroups(final String nodeGroup, final String edgeGroup) {
		this.m_nodeGroup = nodeGroup;
		this.m_edgeGroup = edgeGroup;
	}

	// ------------------------------------------------------------------------

	/**
	 * @see prefuse.action.Action#run(double)
	 */
	@Override
	public void run(final double frac) {
		// perform different actions if this is a run-once or
		// run-continuously layout
		if (this.m_runonce) {
			final Point2D anchor = getLayoutAnchor();

			final Iterator<?> iter = getMagicIterator(this.m_nodeGroup);

			while (iter.hasNext()) {
				final VisualItem item = (NodeItem) iter.next();
				item.setX(anchor.getX());
				item.setY(anchor.getY());
			}
			this.m_fsim.clear();
			long timestep = 1000L;
			initSimulator(this.m_fsim);
			for (int i = 0; i < this.m_iterations; i++) {
				// use an annealing schedule to set time step
				timestep *= (1.0 - (i / (double) this.m_iterations));
				final long step = timestep + 50;
				// run simulator
				this.m_fsim.runSimulator(step);
				// debugging output
				// if (i % 10 == 0 ) {
				// System.out.println("iter: "+i);
				// }
			}
			updateNodePositions();
		} else {
			// get timestep
			if (this.m_lasttime == -1) {
				this.m_lasttime = System.currentTimeMillis() - 20;
			}
			final long time = System.currentTimeMillis();
			final long timestep = Math.min(this.m_maxstep, time - this.m_lasttime);
			this.m_lasttime = time;

			// run force simulator
			this.m_fsim.clear();
			initSimulator(this.m_fsim);
			this.m_fsim.runSimulator(timestep);
			updateNodePositions();
		}
		if (frac == 1.0) {
			reset();
		}
	}

	private void updateNodePositions() {
		final Rectangle2D bounds = getLayoutBounds();
		double x1 = 0, x2 = 0, y1 = 0, y2 = 0;
		if (bounds != null) {
			x1 = bounds.getMinX();
			y1 = bounds.getMinY();
			x2 = bounds.getMaxX();
			y2 = bounds.getMaxY();
		}

		// update positions
		final Iterator<?> iter = getMagicIterator(this.m_nodeGroup);

		while (iter.hasNext()) {
			final VisualItem item = (VisualItem) iter.next();
			final ForceItem fitem = (ForceItem) item.get(FORCEITEM);

			if (item.isFixed()) {
				// clear any force computations
				fitem.force[0] = 0.0f;
				fitem.force[1] = 0.0f;
				fitem.velocity[0] = 0.0f;
				fitem.velocity[1] = 0.0f;

				if (Double.isNaN(item.getX())) {
					super.setX(item, this.referrer, 0.0);
					super.setY(item, this.referrer, 0.0);
				}
				continue;
			}

			double x = fitem.location[0];
			double y = fitem.location[1];

			if (this.m_enforceBounds && (bounds != null)) {
				final Rectangle2D b = item.getBounds();
				final double hw = b.getWidth() / 2;
				final double hh = b.getHeight() / 2;
				if ((x + hw) > x2) {
					x = x2 - hw;
				}
				if ((x - hw) < x1) {
					x = x1 + hw;
				}
				if ((y + hh) > y2) {
					y = y2 - hh;
				}
				if ((y - hh) < y1) {
					y = y1 + hh;
				}
			}

			// set the actual position
			super.setX(item, this.referrer, x);
			super.setY(item, this.referrer, y);
		}
	}

	/**
	 * Reset the force simulation state for all nodes processed by this layout.
	 */
	public void reset() {
		final Iterator<?> iter = getMagicIterator(this.m_nodeGroup);

		while (iter.hasNext()) {
			final VisualItem item = (VisualItem) iter.next();
			final ForceItem fitem = (ForceItem) item.get(FORCEITEM);
			if (fitem != null) {
				fitem.location[0] = (float) item.getEndX();
				fitem.location[1] = (float) item.getEndY();
				fitem.force[0] = fitem.force[1] = 0;
				fitem.velocity[0] = fitem.velocity[1] = 0;
			}
		}
		this.m_lasttime = -1L;
	}

	/**
	 * Loads the simulator with all relevant force items and springs.
	 * 
	 * @param fsim
	 *            the force simulator driving this layout
	 */
	protected void initSimulator(final ForceSimulator fsim) {
		// make sure we have force items to work with
		final TupleSet ts = this.m_vis.getGroup(this.m_nodeGroup);
		if (ts == null) {
			return;
		}
		try {
			ts.addColumns(FORCEITEM_SCHEMA);
		} catch (final IllegalArgumentException iae) { /* ignored */
		}

		float startX = (this.referrer == null ? 0f : (float) this.referrer.getX());
		float startY = (this.referrer == null ? 0f : (float) this.referrer.getY());
		startX = Float.isNaN(startX) ? 0f : startX;
		startY = Float.isNaN(startY) ? 0f : startY;

		Iterator<?> iter = getMagicIterator(this.m_nodeGroup);
		while (iter.hasNext()) {
			final VisualItem item = (VisualItem) iter.next();
			final ForceItem fitem = (ForceItem) item.get(FORCEITEM);
			fitem.mass = getMassValue(item);
			final double x = item.getEndX();
			final double y = item.getEndY();
			fitem.location[0] = (Double.isNaN(x) ? startX : (float) x);
			fitem.location[1] = (Double.isNaN(y) ? startY : (float) y);
			fsim.addItem(fitem);
		}
		if (this.m_edgeGroup != null) {
			iter = getMagicIterator(this.m_edgeGroup);
			while (iter.hasNext()) {
				final EdgeItem e = (EdgeItem) iter.next();
				final NodeItem n1 = e.getSourceItem();
				final ForceItem f1 = (ForceItem) n1.get(FORCEITEM);
				final NodeItem n2 = e.getTargetItem();
				final ForceItem f2 = (ForceItem) n2.get(FORCEITEM);
				final float coeff = getSpringCoefficient(e);
				// coeff = coeff * -40; //alteração
				float slen = getSpringLength(e);
				slen = 80; // alteração
				// System.out.println(slen);
				fsim.addSpring(f1, f2, (coeff >= 0 ? coeff : -1.f), (slen >= 0 ? slen : -1.f));
			}
		}
	}

	/**
	 * Get the mass value associated with the given node. Subclasses should override this method to perform custom mass
	 * assignment.
	 * 
	 * @param n
	 *            the node for which to compute the mass value
	 * @return the mass value for the node. By default, all items are given a mass value of 1.0.
	 */
	protected float getMassValue(final VisualItem n) {
		return 1.0f;
	}

	/**
	 * Get the spring length for the given edge. Subclasses should override this method to perform custom spring length
	 * assignment.
	 * 
	 * @param e
	 *            the edge for which to compute the spring length
	 * @return the spring length for the edge. A return value of -1 means to ignore this method and use the global
	 *         default.
	 */
	protected float getSpringLength(final EdgeItem e) {
		return -1.f;
	}

	/**
	 * Get the spring coefficient for the given edge, which controls the tension or strength of the spring. Subclasses
	 * should override this method to perform custom spring tension assignment.
	 * 
	 * @param e
	 *            the edge for which to compute the spring coefficient.
	 * @return the spring coefficient for the edge. A return value of -1 means to ignore this method and use the global
	 *         default.
	 */
	protected float getSpringCoefficient(final EdgeItem e) {
		return -1.f;
	}

	/**
	 * Get the referrer item to use to set x or y coordinates that are initialized to NaN.
	 * 
	 * @return the referrer item.
	 * @see prefuse.util.PrefuseLib#setX(VisualItem, VisualItem, double)
	 * @see prefuse.util.PrefuseLib#setY(VisualItem, VisualItem, double)
	 */
	public VisualItem getReferrer() {
		return this.referrer;
	}

	/**
	 * Set the referrer item to use to set x or y coordinates that are initialized to NaN.
	 * 
	 * @param referrer
	 *            the referrer item to use.
	 * @see prefuse.util.PrefuseLib#setX(VisualItem, VisualItem, double)
	 * @see prefuse.util.PrefuseLib#setY(VisualItem, VisualItem, double)
	 */
	public void setReferrer(final VisualItem referrer) {
		this.referrer = referrer;
	}

	// ------------------------------------------------------------------------
	// ForceItem Schema Addition

	/**
	 * The data field in which the parameters used by this layout are stored.
	 */
	public static final String FORCEITEM = "_forceItem";
	/**
	 * The schema for the parameters used by this layout.
	 */
	public static final Schema FORCEITEM_SCHEMA = new Schema();
	static {
		FORCEITEM_SCHEMA.addColumn(FORCEITEM, ForceItem.class, new ForceItem());
	}

	public Iterator<?> getMagicIterator(final String group) {

		if (group == this.m_nodeGroup) {
			if (this.magicNodes) {
				return this.m_vis.items(group);
			} else {
				return this.m_vis.visibleItems(group);
			}
		} else {
			if (this.magicEdges) {
				return this.m_vis.items(group);
			} else {
				return this.m_vis.visibleItems(group);
			}
		}
	}

	public boolean hasMagicEdges() {
		return this.magicEdges;
	}

	public void setMagicEdges(final boolean enabled) {
		this.magicEdges = enabled;
	}

	public boolean hasMagicNodes() {
		return this.magicNodes;
	}

	public void setMagicNodes(final boolean enabled) {
		this.magicNodes = enabled;
	}

}